package net.qiqbframework.modelling.command;

import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import net.qiqbframework.commandhandling.CommandMessage;
import net.qiqbframework.common.utils.AnnotationUtil;
import net.qiqbframework.common.utils.ReflectionUtil;
import net.qiqbframework.common.utils.StringUtils;
import net.qiqbframework.messaging.Message;
import net.qiqbframework.modelling.command.voucher.CommandLoadVoucher;

import javax.annotation.Nonnull;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.*;

import static net.qiqbframework.common.BuilderUtils.assertNonNull;


@Slf4j
@ToString
public class AnnotationCommandTargetResolver implements CommandTargetResolver {

    private final List<LoadVoucherAnnotation> aggregateBusinessIdVoucherAnnotations = new ArrayList<>();

    private final Class<? extends Annotation> versionAnnotation;


    public static Builder builder() {
        return new Builder();
    }


    protected AnnotationCommandTargetResolver(Builder builder) {

        this.versionAnnotation = builder.versionAnnotation;
        aggregateBusinessIdVoucherAnnotations.add(new LoadVoucherAnnotation() {

            @Override
            public Class<? extends Annotation> bizIdentifierAnnotation() {
                return BizIdentifierVoucher.class;
            }

            @Override
            public String routingBizIdentifierNamingAttr() {
                return "routingNaming";
            }

            @Override
            public Class<? extends Annotation> versionAnnotation() {
                return VersionVoucher.class;
            }
        });
        for (LoadVoucherAnnotation businessIdVoucherAnnotation : ServiceLoader.load(LoadVoucherAnnotation.class, this.getClass().getClassLoader())) {
            aggregateBusinessIdVoucherAnnotations.add(businessIdVoucherAnnotation);
        }
    }

    @Override
    public CommandLoadVoucher resolveLoadVoucher(@Nonnull CommandMessage<?> command) {
        try {

            final Map<String, Member> businessIds = lookupBusinessIds(command);
            CommandLoadVoucher loadVoucher = new CommandLoadVoucher(findVersion(command));
            for (Map.Entry<String, Member> entry : businessIds.entrySet()) {
                final BizIdentifierVoucherMember bizIdentifierMember = new BizIdentifierVoucherMember(entry.getKey(), entry.getValue());
                if (entry.getValue() instanceof Field f) {
                    final Object fieldValue = ReflectionUtil.getFieldValue(f, command.getPayload());
                    bizIdentifierMember.setValue(fieldValue);
                    loadVoucher.addBizIdentifierMember(bizIdentifierMember);
                }
                if (entry.getValue() instanceof Method m) {
                    ReflectionUtil.ensureAccessible(m);
                    bizIdentifierMember.setValue(m.invoke(command.getPayload()));
                    loadVoucher.addBizIdentifierMember(bizIdentifierMember);
                }
            }
            return loadVoucher;
        } catch (InvocationTargetException e) {
            throw new IllegalArgumentException("An exception occurred while extracting aggregate  information form a command", e);
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException("The current security context does not allow extraction of "
                    + "aggregate information from the given command.", e);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("The value provided for the version is not a number.", e);
        }
    }


    private Map<String, Member> lookupBusinessIds(Message<?> command) throws InvocationTargetException, IllegalAccessException {
        Map<String, Member> businessIdVoucherMembers = new HashMap<>();
        for (LoadVoucherAnnotation annotation : aggregateBusinessIdVoucherAnnotations) {
            for (Method m : ReflectionUtil.methodsOf(command.getPayloadType())) {
                final Optional<Map<String, Object>> annotationAttribute = AnnotationUtil.findAnnotationAttributes(m, annotation.bizIdentifierAnnotation());
                if (annotationAttribute.isPresent()) {
                    ReflectionUtil.ensureAccessible(m);
                    final Object routingName = annotationAttribute.get().get(annotation.routingBizIdentifierNamingAttr());
                    if (StringUtils.isEmptyOrNull(routingName)) {
                        businessIdVoucherMembers.put(m.getName(), m);
                    } else {
                        businessIdVoucherMembers.put(routingName.toString(), m);
                    }
                }
            }
            for (Field f : ReflectionUtil.fieldsOf(command.getPayloadType())) {
                final Optional<Map<String, Object>> annotationAttribute = AnnotationUtil.findAnnotationAttributes(f, annotation.bizIdentifierAnnotation());
                if (annotationAttribute.isPresent()) {
                    ReflectionUtil.ensureAccessible(f);
                    final Object routingName = annotationAttribute.get().get(annotation.routingBizIdentifierNamingAttr());
                    if (StringUtils.isEmptyOrNull(routingName)) {
                        businessIdVoucherMembers.put(f.getName(), f);
                    } else {
                        businessIdVoucherMembers.put(routingName.toString(), f);
                    }
                }
            }
        }
        return businessIdVoucherMembers;

    }

    private Long findVersion(Message<?> command) throws InvocationTargetException, IllegalAccessException {
        return asLong(invokeAnnotated(command, versionAnnotation));
    }

    /**
     * @param command
     * @param annotation
     * @return 第一个为 businessIdName，第二为对应的值
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private static Object invokeAnnotated(Message<?> command, Class<? extends Annotation> annotation) throws InvocationTargetException, IllegalAccessException {
        for (Method m : ReflectionUtil.methodsOf(command.getPayloadType())) {
            if (AnnotationUtil.isAnnotationPresent(m, annotation)) {
                ReflectionUtil.ensureAccessible(m);
                return m.invoke(command.getPayload());
            }
        }
        for (Field f : ReflectionUtil.fieldsOf(command.getPayloadType())) {
            if (AnnotationUtil.isAnnotationPresent(f, annotation)) {
                return ReflectionUtil.getFieldValue(f, command.getPayload());
            }
        }
        return null;
    }

    private Long asLong(Object fieldValue) {
        if (fieldValue == null) {
            return null;
        } else if (fieldValue instanceof Number) {
            return ((Number) fieldValue).longValue();
        } else {
            return Long.parseLong(fieldValue.toString());
        }
    }


    public static final class Builder {

        private Class<? extends Annotation> identifierAnnotation = BizIdentifierVoucher.class;

        private Class<? extends Annotation> versionAnnotation = VersionVoucher.class;


        public Builder targetAggregateIdentifierAnnotation(Class<? extends Annotation> annotation) {
            assertNonNull(annotation, "TargetAggregateIdentifierAnnotation may not be null");
            this.identifierAnnotation = annotation;
            return this;
        }


        public Builder targetAggregateVersionAnnotation(Class<? extends Annotation> annotation) {
            assertNonNull(annotation, "TargetAggregateVersionAnnotation may not be null");
            this.versionAnnotation = annotation;
            return this;
        }

        public AnnotationCommandTargetResolver build() {
            return new AnnotationCommandTargetResolver(this);
        }
    }
}
